从单体走向 Monorepo:一起展App 基于 Melos 的 Flutter 模块化架构重构实践

1. 背景与痛点 (Situation & Task)

在项目(Zhanzhan)初期,为了追求业务的快速迭代,我们采用了传统的 Flutter 单体工程(Monolith)架构。所有的业务逻辑、网络请求、UI 组件和路由都高度集中在同一个 lib 目录下。

随着业务场景的不断丰富(涵盖了“办展”、“看展笔记”、“信息流”、“个人中心”等多个核心域),单体架构的弊端开始集中爆发,主要体现在:

  • 代码边界模糊,陷入“面条代码”: 业务模块之间直接互相 import 页面类,导致严重的隐式耦合。改动“商城”模块的代码,可能会意外引发“看展”模块的崩溃。

  • 依赖冲突频发,包管理失控: 全局共用一个 pubspec.yaml,第三方依赖版本牵一发而动全身,极大地限制了不同业务线的技术选型自由度。

  • 编译与代码生成效率低下: 在单体架构下,运行一次 build_runner 生成代码需要遍历全局,耗时极长,严重影响研发心智和效率。

为了支撑中大型团队的协同开发,抹平不同业务模块的进度差,我们决定彻底打破单体架构,全面向 基于 Melos 的 Monorepo(单体仓库多包管理)架构 演进。

2. 核心架构设计:严谨的“四层洋葱模型” (Action - Architecture)

重构的核心不在于拆分物理文件夹,而在于建立清晰的依赖单向传递规则。我们彻底摒弃了按业务粗粒度划分的思路,而是结合业务现状,采用“细粒度拆分 + 严格分层”的设计,将系统划分为四个独立的生命周期层:

2.1 壳工程层 (Host App: apps/host_app)

  • 定位: 整个 App 的组装流水线。

  • 职责: 自身几乎不包含具体的业务逻辑代码(除了 splash 启动页等强入口逻辑)。它负责引入底层和业务侧的各个 package,完成全局路由表 (app_routes.dart) 的注册、状态管理 (ProviderScope) 的初始化,以及全局依赖注入。

2.2 基础能力层 (Core: packages/core)

  • 定位: 极度稳定、与业务毫无关联的底层基建。

  • 模块:

    • core_network: 基于 Dio 的网络封装、拦截器体系与 Mock 机制。

    • core_router: 基于 GoRouter 的全局路由抽象。

    • core_storage: 本地持久化(Token 管理)。

    • core_analytics: 全局埋点服务。

2.3 公共复用层 (Common: packages/common)

  • 定位: 跨业务模块复用的中间件与共享领域。

  • 模块:

    • auth_session: 用户鉴权守卫与当前用户信息。

    • design_system: 统一的设计语言(UI 组件、颜色、字体)。

    • shared_models: 跨模块的高频实体(如 ExhibitRepository,保证数据一致性)。

2.4 业务功能层 (Features: packages/features)

  • 定位: 并行迭代的业务线,高度内聚。

  • 模块: feature_auth (登录)、feature_home (首页)、feature_exhibit (办展)、feature_note (笔记)。

  • 铁律: 各 feature 包之间绝对物理隔离,禁止相互依赖。跨业务通信统一上浮至壳工程的路由中心或通过协议解耦。

3. 关键技术决策 (Action - Key Decisions)

在此次重构落地过程中,我们做出了几个对后续研发规范具有决定性意义的技术决策:

决策一:采用微模块(Micro-feature)而非宏观领域驱动

在模块划分初期,我们曾面临是建立宏观的“商城”和“看展”大包,还是按实际功能拆分的抉择。考虑到团队规模和业务敏捷度,我们最终选择了以现状为准的细粒度拆分

我们没有预先建立空壳的“业务大组”,而是将 exhibitnote 等功能抽离为独立的 feature。同时,将 auth 这种具有跨域性质的模块下沉,将 splash 等应用生命周期强相关的代码保留在壳工程。这种实事求是的拆解,避免了过度设计,也使得架构不会头重脚轻。

决策二:确立严苛的 Import 路径规范

为了防止 Monorepo 再次退化为单体,我们在代码静态检查中贯彻了基于物理隔离的引用原则:

  • 包内高内聚(自闭环): 同一个 package 内部(例如 feature_auth 内部)互相调用,强制使用相对路径 ../。这保证了模块的完全可移植性。

  • 包间低耦合(物理边界): 跨越 package 借用能力时,严禁使用 ../。必须在 pubspec.yaml 中声明依赖后,使用 package: 绝对路径引入。这为代码审查(Code Review)提供了最直观的边界预警。

决策三:Riverpod 状态与路由的分布式管理

得益于架构分层,我们对状态管理也进行了重组。底层的网络和存储服务在 core 中以 Provider 提供;业务数据层(如 homeNoteRepository)集中在 shared_models 中以便跨域调用;而页面 UI 的状态(如 LoginController, HomeFeedController)则通过 @riverpod 局部封闭在各自的 feature 包内。配合 Melos 的并行脚本,极大地提升了代码生成的效率。

4. 落地收益与未来展望 (Result)

经过数周的奋战,Zhanzhan 项目已全面平稳过渡到 Monorepo 架构。带来的直接收益包括:

  1. 研发效能跃升: 开发“办展”模块的同学只需关注 feature_exhibit,无需关心“笔记”模块的代码变更。同时 melos run build 的并行代码生成,将原本漫长的等待时间缩短了 70% 以上。

  2. 强制的防腐层建设: feature 之间的硬隔离,彻底杜绝了业务间的循环依赖,使代码架构符合开闭原则(OCP)。

  3. 技术栈解绑: 独立的 pubspec.yaml 使得各个模块在未来可以独立升级依赖库,甚至在核心能力层进行灰度技术替换而不影响上层业务。

展望:

随着这套基础设施的落成,我们下一步的重心将转向模块的独立编译与调试。依托 Melos 的能力,我们将为每个核心 Feature 构建独立的 example 工程,使得研发人员在不运行完整壳工程的情况下,也能快速启动并调试单一模块,真正实现从“能跑”向“跑得快”的敏捷研发转型。


项目:zhanzhan(Flutter + Riverpod + Dio · Melos Monorepo)

1. 根目录概览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
zhanzhan/
├── apps/
│ └── host_app/ # 壳工程(主入口、路由、平台工程)
├── packages/
│ ├── core/ # 基础能力层
│ │ ├── core_network/ # 网络层(Dio + 拦截器 + Mock)
│ │ ├── core_storage/ # 本地存储(Token 持久化)
│ │ ├── core_router/ # 路由抽象(GoRouter 封装)
│ │ └── core_analytics/ # 埋点(Matomo)
│ ├── common/ # 公共复用层
│ │ ├── auth_session/ # 鉴权会话(登录失效守卫 + 用户信息)
│ │ ├── design_system/ # 设计系统(主题 / 通用组件)
│ │ └── shared_models/ # 共享模型(跨模块数据 + Repository)
│ └── features/ # 业务功能模块
│ ├── feature_auth/ # 登录 / 注册 / 协议
│ ├── feature_home/ # 首页(信息流 + 视频 + 我的)
│ ├── feature_exhibit/ # 办展(列表 + 快速布展 + 观展 + 详情)
│ ├── feature_note/ # 笔记(详情 + 评论 + 互动)
│ └── feature_splash/ # 启动页
├── docs/ # 项目文档
├── scripts/ # 构建脚本
├── melos.yaml # Melos 配置(Monorepo 管理)
├── pubspec.yaml # 根 workspace pubspec(声明 melos 依赖)
├── analysis_options.yaml # 全局静态检查规则
└── README.md # 项目说明

2. Monorepo 子包总览

层级 包路径 name 说明
壳工程 apps/host_app zhanzhan 主入口、路由注册、平台工程
基础层 packages/core/core_network core_network Dio 实例、拦截器、Mock
基础层 packages/core/core_storage core_storage Token 持久化(SharedPreferences)
基础层 packages/core/core_router core_router GoRouter 抽象、全局导航
基础层 packages/core/core_analytics core_analytics Matomo 埋点服务
公共层 packages/common/auth_session auth_session 鉴权守卫、当前用户服务
公共层 packages/common/design_system design_system 主题颜色/字体/间距 + 通用 UI 组件
公共层 packages/common/shared_models shared_models 跨模块共享的模型与 Repository
功能层 packages/features/feature_auth feature_auth 登录/协议
功能层 packages/features/feature_home feature_home 首页
功能层 packages/features/feature_exhibit feature_exhibit 办展
功能层 packages/features/feature_note feature_note 笔记
功能层 packages/features/feature_splash feature_splash 启动页

3. 壳工程(apps/host_app)

壳工程负责组装各个包,提供应用入口和路由注册。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
apps/host_app/lib/
├── main.dart # 应用入口(ProviderScope、Matomo、依赖注入)
├── app_routes.dart # 集中路由注册(feature 页面绑定 GoRoute)
├── app/ # 预留(当前为空)
├── core/ # 壳工程内部 shim / 胶水代码
│ ├── exceptions/
│ │ └── auth_exceptions.dart # → 转发 core_network
│ ├── navigation/
│ │ └── app_navigator.dart # → 转发 core_router
│ ├── network/
│ │ ├── app_network_config.dart # → 转发 core_network
│ │ ├── auth_interceptor.dart # → 转发 core_network
│ │ ├── dio_provider.dart # → 转发 core_network
│ │ ├── empty_string_interceptor.dart
│ │ ├── media_url_resolver.dart
│ │ └── mock/
│ │ ├── mock_config.dart
│ │ └── mock_interceptor.dart
│ ├── services/
│ │ ├── app_analytics_service.dart # → 转发 core_analytics
│ │ ├── auth_session_guard.dart # → 转发 auth_session
│ │ ├── current_user_service.dart # → 转发 auth_session
│ │ └── token_storage_service.dart # → 转发 core_storage
│ ├── theme/
│ │ ├── app_colors.dart # → 转发 design_system
│ │ ├── app_dimens.dart
│ │ ├── app_text_styles.dart
│ │ └── app_theme.dart
│ └── widgets/
│ ├── app_dialogs.dart # → 转发 design_system
│ ├── app_empty_placeholder.dart
│ ├── app_image_loading_placeholder.dart
│ ├── app_modal_sheet.dart
│ ├── app_share_sheet.dart
│ ├── app_skeleton.dart
│ └── app_top_toast.dart
└── features/ # 壳工程内部 feature shim
├── auth/
│ ├── domain/agreement_type.dart
│ └── presentation/
│ ├── agreement/agreement_page.dart
│ └── login/login_page.dart
├── home/
│ ├── data/home_note_repository.dart
│ └── presentation/home/
│ ├── home_my_page.dart
│ ├── home_note_like_controller.dart
│ └── home_page.dart
├── note/
│ ├── data/note_interaction_repository.dart
│ └── presentation/note_detail_page.dart
└── splash/
└── presentation/splash_page.dart

4. 基础能力层(packages/core)

4.1 core_network(网络层)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
packages/core/core_network/lib/
├── core_network.dart # barrel export
└── src/
├── app_network_config.dart # baseUrl、X-Client-Type
├── auth_error_handler.dart # 鉴权失效协议(Provider 抽象)
├── auth_exceptions.dart # 鉴权异常定义
├── auth_interceptor.dart # 请求附加 token、处理鉴权失效
├── dio_provider.dart # Dio 实例创建(@riverpod)
├── dio_provider.g.dart # 生成代码
├── empty_string_interceptor.dart # 空字符串过滤
├── media_url_resolver.dart # 媒体 URL 解析
└── mock/
├── mock_config.dart # Mock 开关
├── mock_data.dart # Mock 数据
└── mock_interceptor.dart # Mock 拦截器

4.2 core_storage(本地存储)

1
2
3
4
5
packages/core/core_storage/lib/
├── core_storage.dart
└── src/
├── token_storage_service.dart # Token 持久化(SharedPreferences)
└── token_storage_service.g.dart # 生成代码

4.3 core_router(路由抽象)

1
2
3
4
5
packages/core/core_router/lib/
├── core_router.dart
└── src/
├── app_navigator.dart # 全局 navigatorKey
└── app_router.dart # GoRouter 配置 & AppRouter 静态方法

4.4 core_analytics(埋点)

1
2
3
4
packages/core/core_analytics/lib/
├── core_analytics.dart
└── src/
└── app_analytics_service.dart # Matomo Tracker 封装

5. 公共复用层(packages/common)

5.1 auth_session(鉴权会话)

1
2
3
4
5
packages/common/auth_session/lib/
├── auth_session.dart
└── src/
├── auth_session_guard.dart # 登录失效统一处理
└── current_user_service.dart # 当前用户信息服务

5.2 design_system(设计系统)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
packages/common/design_system/lib/
├── design_system.dart
└── src/
├── theme/
│ ├── app_colors.dart
│ ├── app_dimens.dart
│ ├── app_text_styles.dart
│ └── app_theme.dart
└── widgets/
├── app_dialogs.dart
├── app_empty_placeholder.dart
├── app_image_loading_placeholder.dart
├── app_modal_sheet.dart
├── app_share_sheet.dart
├── app_skeleton.dart
└── app_top_toast.dart

5.3 shared_models(共享模型)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
packages/common/shared_models/lib/
├── shared_models.dart
└── src/
├── app_state/
│ └── cross_feature_state.dart # 跨功能状态
├── exhibit/
│ ├── exhibit_category.dart
│ ├── exhibit_category_tree_node.dart
│ ├── exhibit_chat_message.dart
│ ├── exhibit_comment_model.dart
│ ├── exhibit_cover_resolver.dart
│ ├── exhibit_exhibition_detail.dart
│ ├── exhibit_frame_material.dart
│ ├── exhibit_hall.dart
│ ├── exhibit_hall_template_detail.dart
│ ├── exhibit_music.dart
│ ├── exhibit_repository.dart # 办展 Repository(Dio 接口)
│ └── fast_exhibit_draft_store.dart # 快速布展草稿管理
├── home/
│ ├── home_note_like_controller.dart
│ └── home_note_repository.dart # 首页笔记 Repository
├── note/
│ └── note_interaction_repository.dart # 笔记互动 Repository
└── widgets/
└── exhibit_hall_grid.dart # 展馆网格组件

6. 业务功能模块(packages/features)

6.1 feature_auth(登录/注册/协议)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
packages/features/feature_auth/lib/
├── feature_auth.dart
└── src/
├── data/
│ ├── agreement_repository.dart # 协议接口
│ ├── auth_repository.dart # 鉴权接口(验证码登录)
│ └── auth_repository.g.dart
├── domain/
│ ├── agreement.dart
│ ├── agreement_type.dart
│ └── login_form.dart
└── presentation/
├── agreement/
│ ├── agreement_page.dart
│ └── agreement_provider.dart
└── login/
├── login_controller.dart
├── login_controller.g.dart
└── login_page.dart

6.2 feature_home(首页)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
packages/features/feature_home/lib/
├── feature_home.dart
└── src/
├── domain/
│ ├── home_note.dart
│ └── home_video.dart
└── presentation/
├── widgets/
│ └── custom_curated_nav_bar.dart
└── home/
├── home_create_note_page.dart
├── home_edit_profile_page.dart
├── home_feed_controller.dart / .g.dart
├── home_my_page.dart
├── home_page.dart
├── home_video_controller.dart / .g.dart
├── home_video_interaction_controller.dart / .g.dart
├── home_video_tab_page.dart
├── qr_scan_page.dart
└── widgets/
└── home_note_media_cover.dart

6.3 feature_exhibit(办展)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
packages/features/feature_exhibit/lib/
├── feature_exhibit.dart
└── src/
├── domain/
│ └── exhibit_hall_draft.dart
└── presentation/
├── cover/
│ └── exhibit_cover_page_template_1.dart
├── detail/
│ ├── exhibit_artwork_comment_controller.dart
│ ├── exhibit_artwork_comment_page_template_1.dart
│ ├── exhibit_artwork_detail_page_template_1.dart
│ ├── exhibit_detail_page_template_1.dart
│ ├── exhibit_detail_page_type_1.dart
│ ├── exhibit_detail_page_type_2.dart
│ ├── exhibit_detail_page_type_3.dart
│ ├── exhibit_hall_draft_controller.dart
│ ├── exhibit_hall_template_detail_controller.dart
│ ├── exhibit_hall_template_detail_page.dart
│ └── exhibition_3d_page.dart
├── exhibit_tab/
│ ├── exhibit_tab_controller.dart / .g.dart
│ └── exhibit_tab_page.dart
├── fast_exhibit/
│ ├── fast_exhibit_page.dart
│ ├── fast_exhibit_publish_page.dart
│ └── widgets/
│ └── fast_exhibit_sheets.dart
├── viewing/
│ ├── exhibit_viewing_page_template_1.dart
│ └── exhibit_viewing_scene_mock_store.dart
└── widgets/
├── exhibit_info_widgets.dart
└── exhibit_wall_artworks_perspective.dart

6.4 feature_note(笔记)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
packages/features/feature_note/lib/
├── feature_note.dart
└── src/
├── data/
│ └── note_comment_repository.dart
├── domain/
│ └── note_comment_model.dart
└── presentation/
├── note_comment_controller.dart
├── note_detail_controller.dart
├── note_detail_page.dart
├── note_interaction_controller.dart
└── widgets/
├── note_comment_item.dart
├── note_comment_section.dart
├── note_detail_bottom_bar.dart
└── note_image_gallery_page.dart

6.5 feature_splash(启动页)

1
2
3
4
packages/features/feature_splash/lib/
├── feature_splash.dart
└── src/
└── splash_page.dart

7. 数据接口与 Repository 映射

详细接口原始文档见:docs/api.md

7.1 鉴权/用户相关

  • AuthRepositoryfeature_auth

    • GET /sms/amaz/wxapp/send_sms(发送验证码)
    • POST /sms/amaz/wxapp/verify_sms(验证码登录)
  • AgreementRepositoryfeature_auth

    • GET /user/sys/agreement/get(协议内容)
  • CurrentUserServiceauth_session

    • GET /user/amaz/user/info(用户信息)
    • POST /user/amaz/user/update(更新资料)

7.2 办展相关

  • ExhibitRepositoryshared_models
    • 分类:/api/exhibition/category/list/api/exhibition/category/tree
    • 音乐:/api/music/system/normal
    • 展览列表/详情:/api/exhibition/list/api/exhibition/{id}
    • 展览流:/api/information/exhibition/get
    • 互动聊天:/api/exhibition/interact/chat/list/api/exhibition/interact/chat/add
    • 评论:/api/exhibition/comment/list/api/exhibition/comment/add/api/exhibition/interact/comment/delete
    • 模板:/api/exhibition/hall/template/{templateId}/api/exhibition/hall/template/page
    • 发布:/api/exhibition/saveOrUpdate

7.3 首页/笔记相关

  • HomeNoteRepositoryshared_models

    • 信息流:/api/information/follow/get
    • 笔记列表:/api/information/note/get
    • 点赞列表:/api/note/take/home/like/list
    • 评论列表:/api/note/take/home/list
    • 详情:/api/note/take/detail
    • 创建/编辑:/api/note/edit/create_modify
  • NoteCommentRepositoryfeature_note

    • 评论列表:/api/note/take/list
    • 评论新增:/api/note/take/add
  • NoteInteractionRepositoryshared_models

    • 关注:/user/amaz/user/add/user/amaz/user/cancel
    • 点赞/收藏:/user/like/divide/add/user/like/divide/cancel

8. Provider 与状态管理分布

  • 全局网络与服务:
    • dioProvidercore_network
    • tokenStorageServiceProvidercore_storage
    • currentUserServiceProviderauth_session
    • authErrorHandlerProvidercore_network,壳工程注入实现)
  • 功能 Repository Provider:
    • authRepositoryProviderfeature_auth
    • agreementRepositoryProviderfeature_auth
    • exhibitRepositoryProvidershared_models
    • homeNoteRepositoryProvidershared_models
    • noteCommentRepositoryProviderfeature_note
    • noteInteractionRepositoryProvidershared_models
  • 控制器(@riverpod / Notifier):
    • LoginControllerfeature_auth
    • ExhibitTabControllerfeature_exhibit
    • HomeFeedControllerfeature_home
    • HomeVideoControllerfeature_home
    • HomeVideoInteractionControllerfeature_home
    • NoteDetailControllerfeature_note
    • NoteCommentControllerfeature_note
    • NoteInteractionControllerfeature_note

分享到:

评论完整模式加载中...如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理